package dominio;

import java.util.ArrayList;

/**
 * Created by Rafael Simon Garcia on 12/8/13.
 */
public class SimulatedAnnealing extends OptimizationAlgorithm{
    private static KirkPatrickCoolingIterator coolingSchedule;
    private static double startTemperature;

    //Setup method that receives the the original nodes list and the max amount of iterations
    public static void Setup(ArrayList <? extends GraphElement> nodes, int iterations, double startTemp, double alphaFactor){
        nodesList = nodes;
        maxIterations = iterations;
        startTemperature = startTemp;
        coolingSchedule = new KirkPatrickCoolingIterator(startTemp, alphaFactor);
    }

    public static void Setup(ArrayList <? extends GraphElement> nodes, int iterations, double startTemp, double alphaFactor, boolean operator){
        nodesList = nodes;
        maxIterations = iterations;
        startTemperature = startTemp;
        operatorType = operator;
        coolingSchedule = new KirkPatrickCoolingIterator(startTemp, alphaFactor);

    }

    public static PathOptimizationResult OptimizePath(){
        if (nodesList.size() == 0) return null; // error escaping

        ArrayList<Integer> bestTour;
        ArrayList<HillClimbingResult> results = new ArrayList<>();

        bestTour = DefaultTour(nodesList.size());

        try{
            //get the results
            Class op = operatorType ? SwitchTasksOperator.class : ReverseSectionsOperator.class;
            SimulatedAnnealingFitnessCalculator.Reset();
            results.add(PerformSimulatedAnnealing(bestTour, op, SimulatedAnnealingFitnessCalculator.class, maxIterations));
        }catch (Exception e){}
        finally {
            PathOptimizationResult pathOptResult;

            //if the results ArrayList is empty return null, otherwise wrap the results and the rearranged node list
            //into a PathOptimizationResults
            pathOptResult = (results.isEmpty()) ? null : new PathOptimizationResult(results, RearrangeNodesFromTour(results.get(0).tour));

            return pathOptResult;
        }
    }

    private static HillClimbingResult PerformSimulatedAnnealing(ArrayList<Integer> currentTour, Class op, Class c, int maxIters) throws Exception {
        ArrayList<Integer> nextTour;
        boolean done;
        int currentTourCost, nextTourCost, iterations = 0;
        Double temp, prob;

        op.getDeclaredMethod("InitializeOperator", Integer.TYPE).invoke(null, currentTour.size());

        SetupAlgorithmReflection(op, c);

        currentTourCost = (Integer)Calculate.invoke(null, currentTour, nodesList);

        //iterate over the cooling schedule
        while((temp = coolingSchedule.NextTemperature())!= null){
            done = false;

            //iterate over the permutations of the operator
            while((Boolean)HasNextValue.invoke(null)){
                // if I've maxed my iterations then break the loop over the operator
                if(iterations >= maxIters){
                    done = true;
                    break;
                }

                //get the next tour
                nextTour = (ArrayList<Integer>)Operate.invoke(null, currentTour);

                if(nextTour.isEmpty()) continue; //empty nextTour possible due to i = j on the permutations iterator

                nextTourCost = (Integer)Calculate.invoke(null, nextTour, nodesList); //calculate nextTour length

                iterations += 1; //increase my iterations



                prob = ProbabilityOfBetterSolution(currentTourCost, nextTourCost, temp);

                //choose the next solution as the current solution based on the probability distribution
                if(Math.random() < prob){
                    currentTour = nextTour;
                    currentTourCost = nextTourCost;
                    break;
                }
            }

            op.getDeclaredMethod("Reset",null).invoke(null); //reset the operator if it needs to be reset

            if(done) //no more iterations available; finish iterating over the cooling schedule
                break;
        }

        return new HillClimbingResult(iterations, SimulatedAnnealingFitnessCalculator.bestScore, SimulatedAnnealingFitnessCalculator.best);
    }

    public static double ProbabilityOfBetterSolution(int currentScore, int nextScore, double temperature){
        //always choose a better path if available, otherwise return the probability of switching to a higher energy state
        return (nextScore > currentScore) ? 1.0 : Math.exp(- Math.abs(nextScore - currentScore) / temperature);
    }

}
